#pragma once
#include <jsoncpp/json/json.h>
#include <atomic>

#include "../Common/Util.hpp"
#include "../Common/Log.hpp"
#include "complier.hpp"
#include "runner.hpp"

class CompileAndRun{
public:

    //采取的方案是：使用ms级别的时间戳 + 一个原子计数器，形成一个唯一的文件名
    static std::string MakeUniqueFileName(){
        //atomic本身就是一个原子的计数器，所以是线程安全的
        static std::atomic_uint id(0);   //用一个static的，要不然每次进入该函数都会重新定义
        ++id;
        int time = TimeUtil::GetTimeStamp_ms();
        return std::to_string(time) + "_" + std::to_string(id);
    }


    //这里需要有一个接口：通过状态码得到对应的信息
    //针对于编译错误，我们其实更想让对方知道的reason是报错的信息！
    static std::string StatuCodeToDesc(int code, const std::string& name){
        std::string desc;
        switch(code){
            //还可以慢慢补充
            case 0:  break;
            case -1: desc = "Code Is Empty"; break;
            case -2: desc = "Unknown Error"; break;
            case -3: FileUtil::ReadFromFile(PathUtil::Compile_err(name), &desc, true); break;
            case SIGABRT: desc = "Memory Limit Exceeded"; break;
            case SIGXCPU: desc = "CPU Timeout"; break;
            case SIGFPE: desc = "Float Point Error"; break;
            default: desc = "Unknown Code : " + std::to_string(code); break;
        }
        return desc;
    }

    static void RemoveTempFile(const std::string& name){
        std::string _src = PathUtil::Src(name);
        if(FileUtil::IsFileExist(_src)) unlink(_src.c_str());
        
        std::string _exe = PathUtil::Exe(name);
        if(FileUtil::IsFileExist(_exe)) unlink(_exe.c_str());

        std::string _complie_err = PathUtil::Compile_err(name);
        if(FileUtil::IsFileExist(_complie_err)) unlink(_complie_err.c_str());

        std::string _stdin = PathUtil::Stdin(name);
        if(FileUtil::IsFileExist(_stdin)) unlink(_stdin.c_str());
        
        std::string _stdout = PathUtil::Stdout(name);
        if(FileUtil::IsFileExist(_stdout)) unlink(_stdout.c_str());

        std::string _stderr = PathUtil::Stderr(name);
        if(FileUtil::IsFileExist(_stderr)) unlink(_stderr.c_str());
    }


    //这个接口的作用是：接收一个输入的json串，然后再进行操作，最后填充out_json返回给客户端
    /****
     * statu_code < 0  内部错误
     * statu_code > 0  运行错误
     * statu_code == 0 运行成功
     */
    static void Start(const std::string& in_json, std::string* out_json){
        //1.先把输入的字符串，以json的形式，解析到root，形成一系列的key_value结构
        Json::Value in_root;
        Json::Reader reader;
        reader.parse(in_json, in_root);

        //2.把输入字符串的相关信息解析出来
        std::string code = in_root["code"].asString(), input = in_root["input"].asString();
        int cpu_limit = in_root["cpu_limit"].asInt(), mem_limit = in_root["mem_limit"].asInt();

        //先行声明一下后续要用的变量，方便逻辑优化！(因为语句和goto之间不能定义变量)
        int run_code = 0;               //运行返回的状态码
        int statu_code = 0;             //请求返回的状态码
        std::string name;               //文件唯一的名字
        Json::Value out_root;           //输出json串要用到的value

        //这里可以做一个特判：如果code的大小为0，说明是空的，就没必要走后面的逻辑 
        if(code.size() == 0){
            statu_code = -1;   //文件为空
            goto END;
        }

        //3.现在我们得到了代码文本，但是我们要有地方存放它！
        //而且，因为很可能同时会有多个客户端请求，我们需要生成一个唯一的文件名才行！
        name = MakeUniqueFileName();   //但这里仅仅是文件名，是没有后缀和路径的！

        //4.生成文件名后，我们需要把代码的内容写入到文件内
        if(!FileUtil::WriteIntoFile(PathUtil::Src(name), code)){
            //需要针对于错误的情况去判断
            statu_code = -2;    //内部的一些错误
            goto END;
        }

        //5.进行编译操作
        if(!Compiler::ComPile(name)){
            statu_code = -3;   //-3专指编译错误
            goto END;
        }

        //6.进行运行操作
        run_code = Runner::Run(name, cpu_limit, mem_limit);   //我们是对Run的返回值做了详细定义的！
        if(run_code > 0) statu_code = run_code;               //>0  : 收到信号，说明是运行出错
        else if(run_code < 0) statu_code = -2;                //<0  : 内部的一些错误(如子进程创建失败，读取失败...)
        else statu_code = 0;                                  //==0 : 正常运行(结果是否正确不关注)

        END:

        //走到这里，就需要想办法对返回的json串做填充了
        out_root["statu"] = statu_code;
        out_root["reason"] = StatuCodeToDesc(statu_code, name);    

        std::string _stdout, _stderr;   //选填字段
        if(statu_code == 0){
            FileUtil::ReadFromFile(PathUtil::Stdout(name), &_stdout, true);
            FileUtil::ReadFromFile(PathUtil::Stderr(name), &_stderr, true);
            out_root["stdout"] = _stdout;
            out_root["stderr"] = _stderr;
        }
        //输出json串
        Json::StyledWriter sw;
        *out_json = sw.write(out_root);

        //删除本次编译运行所产生的所有可能存在的临时文件
        RemoveTempFile(name);       //先不进行删除，等到上线了再来
    }
};


/*******************
 * 首先，我们需要指明输入的json是什么样的：
 * code： 代码文本 "#include..."
 * input: 用户给自己提交的代码对应的输入，先不做处理
 * cpu_limit: xxx
 * mem_limit: yyy
 ***********************/



/*******************
 * 其次，我们需要指明返回的json是什么样的：
 * statu：状态码，表示状态(表示的是整个操作流程的)
 * reason: 此次请求的结果
 * 
 * 选填字段：
 * stdout: 如果有成功运行，这里填充的是运行结果
 * stderr: 如果运行的时候出现了一些错误，那么这里填充的是运行的相关错误
 ***********************/